package suncertify.application;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Properties;

import suncertify.application.exceptions.NullEmptyParameterException;

/**
 * This class represents a persistent set of application properties. The properties are saved in the applications
 * properties file which is name by <code>CONFIGURATION_FILENAME</code> constant and is located in the working directory
 * of the application.
 * 
 * <p>
 * The following application properties are persisted:
 * <ol>
 * <li><b>Database file path.</b> The path of the database file provided by the end user in
 * {@link ApplicationMode#SERVER} and {@link ApplicationMode#STANDALONE_CLIENT} application modes.</li>
 * <li><b>Server computer name.</b> Represents the name of the server computer provided by the end user in
 * {@link ApplicationMode#NETWORK_CLIENT} mode.</li>
 * <li><b>Server port number.</b>Represents the port number of the server computer provided by the end user in
 * {@link ApplicationMode#NETWORK_CLIENT} mode.</li>
 * </ol>
 * 
 * <p>
 * This class is implemented as a singleton class, this guarantees that only one instance of this class is created in
 * the application. The first access to the {@link #getInstance()} method creates the only instance of the class, and
 * initializes the class. The class is initialized once in the {@link Application#initializeApplication()} method.
 * 
 * <p>
 * Notes:
 * <ul>
 * <li>
 * When the class initializes it loads the application properties file which is expected to be located in the working
 * directory of the user who is running the application, the properties of the file are read from the file system and
 * cached in the class. Any access to the properties retrieves the cached values.</li>
 * <li>The properties file is not part of the application itself, it gets created when the first {@link #saveToFile()}
 * is called.</li>
 * <li>The property values are not validated by this class in any form, it is the responsibility of the caller to
 * validate the values. The property values are also not validated when they are read from the file, any manual changes
 * of the property values in done manually in the properties file are not checked in any form. The application assumes
 * that it is the only authority than writes and reads properties.</li>
 * <li>The class only identifies the above listed three properties trying to save/read other properties will throw
 * <code>IllegarArgumentException</code></li>
 * <li>The class uses internally the standard java {@link Properties} class.</li>
 * <li>Any errors that happen during initialization (e.g. file IO errors) are saved by the class.</li>
 * 
 * @see ClientApplication
 * @see ServerApplication
 * @see Application
 */
public final class UserConfiguration {

    /**
     * Constant definition for user directory, see {@link System#getProperty(String)} for details.
     */
    public static final String SYSTEMPROPERTY_USER_DIR = "user.dir";

    /**
     * Constant that defines the name of the applications properties file.
     */
    private static final String CONFIGURATION_FILENAME = "suncertify.properties";

    /**
     * Constant that defines the name of the database location property name.
     */
    public static final String PROP_DATABASE_LOCATION = "databaseLocation";

    /**
     * Constant that defines the name of the server name property name.
     */
    public static final String PROP_SERVER_NAME = "serverName";

    /**
     * Constant that defines the name of the server port property name.
     */
    public static final String PROP_SERVER_PORT = "serverPort";

    /**
     * A list that contains the valid three property names.
     */
    public ArrayList<String> propertyNames = new ArrayList<String>();

    /**
     * Saves any error messages that happened during class initialization e.g. file IO errors.
     */
    private String initializationMessage = null;

    /**
     * Represents the properties file.
     */
    private File configFile;

    /**
     * Represents the collection of property names and values.
     */
    private Properties properties = new Properties();

    /**
     * Holds the singleton instance of the class.
     */
    private static UserConfiguration singletonInstance = null;

    /**
     * Constructor does all necessary initialization of the class. The constructor reads the properties file from the
     * current user directory and caches all the properties in the {@link #properties} member variable. Any errors that
     * happen during the file access are saved in the {@link #initializationMessage} member variable.
     */
    private UserConfiguration() {

	this.propertyNames.add(PROP_DATABASE_LOCATION);
	this.propertyNames.add(PROP_SERVER_NAME);
	this.propertyNames.add(PROP_SERVER_PORT);

	String configFilePath = System.getProperty(SYSTEMPROPERTY_USER_DIR);
	configFile = new File(configFilePath, CONFIGURATION_FILENAME);

	if (configFile.exists()) {
	    if (configFile.canRead()) {

		FileInputStream fis = null;

		try {
		    fis = new FileInputStream(configFile);
		    properties.load(fis);
		    fis.close();

		    Log.logInfo("suncertify.properties file loaded and closes successfully.");

		} catch (FileNotFoundException e) {
		    Log.logInfo("User configuration file (suncertify.properties) not found!");
		} catch (IOException e) {
		    Log.logInfo("IOException while reading config file!");
		    this.initializationMessage = Strings.MSG_CONFIG_FILE_READ_ERROR;
		}
	    } else {
		Log.logInfo("properties file cannot be read!");
		this.initializationMessage = Strings.MSG_CONFIG_FILE_READ_PERMISSION_ERROR;
	    }
	} else {
	    Log.logInfo("User configuration file (suncertify.properties) not found!");
	}
    }

    /**
     * Returns the initialization error message. Used to inform the user about any properties file access or permission
     * errors.
     * 
     * @return The initialization error message.
     */
    public String getInitializationMessage() {

	return this.initializationMessage;
    }

    /**
     * Returns the property value for the given property name. The value is read from the class' internal cache.
     * 
     * @param propertyName
     *            The name of the property to be read.
     * @return The property value.
     * @throws NullEmptyParameterException
     *             if the <code>propertyName</code> parameter is null or empty.
     * @throws IllegalArgumentException
     *             if the <code>propertyName</code> is not equal to one of the public property name constants.
     */
    public String getProperty(String propertyName) {

	if (propertyName == null || propertyName.isEmpty()) {

	    throw new NullEmptyParameterException("propertyName");
	}

	// check if propertyName is a valid name
	if (!this.propertyNames.contains(propertyName)) {
	    throw new IllegalArgumentException(Strings.getMessage(Strings.MSG_INVALID_PROPERTY_NAME, propertyName));
	}

	return properties.getProperty(propertyName);
    }

    /**
     * Saves the property name and value pair in the class' internal cache. This function does not save the name and
     * value pair to the properties file.
     * 
     * @param propertyName
     *            Name of the property to be saved, must be one of the three public property name constants.
     * @param propertyValue
     *            Value of the property.
     * 
     * @throws NullEmptyParameterException
     *             Thrown if the <code>propertyName</code> is null or empty or the <code>propertyValue</code> is null or
     *             empty.
     *@throws IllegalArgumentException
     *             if the <code>propertyName</code> is not one of the public three property name constants.
     */
    public void setProperty(String propertyName, String propertyValue) {

	if (propertyName == null || propertyName.isEmpty()) {

	    throw new NullEmptyParameterException("propertyName");
	}

	// check if propertyName is valid name
	if (!this.propertyNames.contains(propertyName)) {
	    throw new IllegalArgumentException(Strings.getMessage(Strings.MSG_INVALID_PROPERTY_NAME, propertyName));
	}

	if (propertyValue == null || propertyValue.isEmpty()) {

	    throw new NullEmptyParameterException("propertyValue");
	}

	properties.setProperty(propertyName, propertyValue);
    }

    /**
     * Saves the cached property name and value pairs to the applications properties file.
     * 
     * @throws IOException
     *             save operation to the properties file fails.
     */
    public void saveToFile() throws IOException {

	FileOutputStream fos = new FileOutputStream(configFile);

	try {
	    properties.store(fos, Strings.CAP_CONFIG_FILE_HEADER);
	} finally {
	    fos.close();
	}
    }

    /**
     * Singleton implementation of the class. This function returns the reference to the class instance. The first call
     * to this function invokes the the class' constructor which initializes the class. The reference is stored in the
     * {@link #singletonInstance} member variable.
     * 
     * @return The singleton instance of the class.
     */
    public static UserConfiguration getInstance() {

	if (singletonInstance == null) {

	    singletonInstance = new UserConfiguration();
	}

	return singletonInstance;
    }
}
